/*
 * GSeason.java
 * Created on 26 août 2007
 *
 * Glacéo Internet Platform
 * http://sourceforge.net/projects/chm/
 *
 * Copyright (c) 2005-2007, Jan Janke (VirtualHockey Project)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hockeo.vhbase.data.dao.season;

import java.util.GregorianCalendar;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.hockeo.vhbase.data.dao.IGEntity;

/**
 * Represents a single hockey season.
 *
 * @version $Id$
 * @author jjanke
 */
@Entity
@Table( name = "h_season" )
public final class GSeason implements IGEntity
{
  @Id
  @Column( name = "id" )
  private int               d_nId;

  @Column( name = "name" )
  private String            d_strName;

  @Column( name = "active" )
  private boolean           d_fActive        = false;

  @Column( name = "started" )
  private boolean           d_fStarted       = false;

  @Column( name = "finished" )
  private boolean           d_fFinished      = false;

  @Column( name = "startdate" )
  private GregorianCalendar d_calStartDate   = null;

  @Column( name = "enddate" )
  private GregorianCalendar d_calEndDate     = null;

  /**
   * Special field that can be used to indicate that data is or is not available for the
   * given season. The context is freely chooseable. This field can be useful especially
   * when exposing a season selector to the user interface. In some seasons no data may be
   * available for a season. By asking this field the UI may adapt its state accordingly.
   */
  @Transient
  private transient boolean d_fDataAvailable = false;

  /**
   * Creates a new season with the specified id. The season name will be left empty. It
   * can be either set manually by directly accessing the database or it will be computed
   * when the season is marked as being started.
   *
   * @param nId the ID of the newly created season.
   */
  public GSeason( int nId )
  {
    d_nId = nId;
    generateName();
  }

  /** Default parameterless constructor. */
  protected GSeason()
  {}

  /**
   * Indicates whether this season is the active season.
   */
  public boolean isActive()
  {
    return d_fActive;
  }

  /**
   * Allows to set whether this season is active or not.
   *
   * @param flag the new active/inactive setting for the season
   */
  public void setActive( boolean flag )
  {
    if ( !flag && isStarted() && !isFinished() )
      throw new IllegalStateException( "Failed to mark season as not active as the season has been started "
          + "but not yet marked as being finished." );

    d_fActive = flag;
  }

  /**
   * Resets the STARTED and FINISHED flags as well as start and end dates. Should be used
   * with great care!
   */
  public void reset()
  {
    d_fStarted = false;
    d_fFinished = false;
    d_calStartDate = null;
    d_calEndDate = null;
  }

  /**
   * @return Indicates whether the season has been started.
   */
  public boolean isStarted()
  {
    return d_fStarted;
  }

  /**
   * Allows to set whether the season is started or not.
   */
  public void markAsStarted()
  {
    if ( d_fStarted )
      throw new IllegalArgumentException( "The season has already been started." );

    d_fStarted = true;
    d_calStartDate = new GregorianCalendar();

    // if the season name is null, we create a provisory name
    //
    if ( d_strName == null )
      generateName();
  }

  /**
   * Generates a new name for the season.
   */
  private void generateName()
  {
    if ( d_calStartDate == null )
      d_calStartDate = new GregorianCalendar();

    int nYear4 = d_calStartDate.get( GregorianCalendar.YEAR );

    // Determine 2-digit year. The division by 100 followed by the multiplication
    // is done to get rid of the last two digits and have a proper divisor for the
    // modulo operation.
    //
    int nYear2 = nYear4 % ( ( nYear4 / 100 ) * 100 );

    d_strName = nYear4 + "/";

    if ( nYear2 < 10 )
      d_strName += "0";

    d_strName += nYear2 + "-";

    if ( d_nId < 10 )
      d_strName += "0";

    d_strName += d_nId;
  }

  /**
   * @return Indicates whether the season is finished.
   */
  public boolean isFinished()
  {
    return d_fFinished;
  }

  /**
   * Allows to set whether the season is finished or not. This method only sets the
   * 'finished' flag and does not modify the season end date. When the finished flag is
   * set, the season is considered to be finished because all administrative end of season
   * tasks are executed. However, as long as no new season has been created, the actual
   * season end date should not be reset. The end date is used to determine the current
   * season.
   */
  public void markAsFinished()
  {
    if ( !d_fStarted )
      throw new IllegalStateException( "The season must be started first." );

    if ( d_fFinished )
      throw new IllegalStateException( "The season has already been finished." );

    d_fFinished = true;

    // Potentially update season name if the end year is not equal to the start year
    //
    d_calEndDate = new GregorianCalendar();

    // Split season name (we have to check if the name is correct).
    // Index 0: [4 digit start year]/[2 digit end year]
    // Index 1: [season id]
    //
    String[] astrNameParts = d_strName.split( "-" );

    // Determine ending year which is part of the current name
    // Index 0: [4 digit start year]
    // Index 1: [2 digit end year]
    //
    String[] astrYears = astrNameParts[0].split( "/" );
    int nEndYear = Integer.parseInt( astrYears[1] );

    // Determine actual ending year
    //
    int nYear4 = d_calEndDate.get( GregorianCalendar.YEAR );

    // Determine 2-digit year. The division by 100 followed by the multiplication
    // is done to get rid of the last two digits and have a proper divisor for the
    // modulo operation.
    //
    int nYear2 = nYear4 % ( ( nYear4 / 100 ) * 100 );

    // If the currently set and the actual ending year are not the same, we modify the
    // season name
    //
    if ( nEndYear != nYear2 )
    {
      d_strName = astrYears[0] + "/";

      if ( nYear2 < 10 )
        d_strName += "0";

      d_strName += nYear2 + "-" + astrNameParts[1];
    }
  }

  /**
   * Marks the season as not being finished anymore. Should be used with great care!
   */
  public void resetFinishedFlag()
  {
    if ( !d_fFinished )
      throw new IllegalStateException( "The season has not yet been finished." );

    d_fFinished = false;
  }

  /**
   * @return Returns the official season name (e.g. 2007/07-01).
   */
  public String getName()
  {
    return d_strName;
  }

  /**
   * @return The date of the start of the season (<code>null</code> if not set).
   */
  public GregorianCalendar getStartDate()
  {
    return d_calStartDate;
  }

  /**
   * @return The date of the end of the season (<code>null</code> if not set).
   */
  public GregorianCalendar getEndDate()
  {
    return d_calEndDate;
  }

  /**
   * Indicates if data is available for the given season. What kind of data is meant
   * depends on the actual usage context. In most situations, the method result is
   * <code>false</code>.
   */
  public boolean isDataAvailable()
  {
    return d_fDataAvailable;
  }

  /**
   * Allows to set whether data is available for a season. What data is meant depends on
   * the actual context.
   *
   * @param flag indicates whether data is available for this season or not
   */
  public void setDataAvailable( boolean flag )
  {
    d_fDataAvailable = flag;
  }

  /**
   * @return Returns the official season ID.
   */
  public int getId()
  {
    return d_nId;
  }

  public Class<? extends IGEntity> getGEntityClass()
  {
    return GSeason.class;
  }

  public boolean instanceOf( Class<? extends IGEntity> classOther )
  {
    return classOther.isAssignableFrom( getGEntityClass() );
  }

  /*
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals( Object obj )
  {
    try
    {
      return d_nId == ( (GSeason) obj ).getId();
    }
    catch ( Exception ex ) // especially ClassCastException(s) may happen
    {
      return false;
    }
  }

  /*
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode()
  {
    return d_nId;
  }

  /*
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString()
  {
    return "Season " + d_strName;
  }
}
